{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Hello Node Kubernetes\n",
    "\n",
    "**Learning Objectives**\n",
    " * Create a Node.js server\n",
    " * Create a Docker container image\n",
    " * Create a container cluster and a Kubernetes pod\n",
    " * Scale up your services"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Overview\n",
    "\n",
    "The goal of this hands-on lab is for you to turn code that you have developed into a replicated application running on Kubernetes, which is running on Kubernetes Engine. For this lab the code will be a simple Hello World node.js app.\n",
    "\n",
    "Here's a diagram of the various parts in play in this lab, to help you understand how the pieces fit together with one another. Use this as a reference as you progress through the lab; it should all make sense by the time you get to the end (but feel free to ignore this for now).\n",
    "\n",
    "<img src='../assets/k8s_hellonode_overview.png' width=\"500\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create a Node.js server\n",
    "\n",
    "The file `./src/server.js` contains a simple Node.js server. Use `cat` to examine the contents of that file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!cat ./src/server.js"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Start the server by running `node server.js` in the cell below. Open a terminal and type\n",
    "```bash\n",
    "curl http://localhost:8000\n",
    "```\n",
    "to see what the server outputs. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!node ./src/server.js"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should see the output `\"Hello World!\"`. Once you've verfied this, interupt the above running cell by hitting the stop button."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create and build a Docker image\n",
    "\n",
    "Now we will create a docker image called `hello_node.docker` that will do the following:\n",
    "\n",
    " 1. Start from the node image found on the Docker hub by inhereting from `node:6.9.2`\n",
    " 2. Expose port 8000\n",
    " 3. Copy the `./src/server.js` file to the image\n",
    " 4. Start the node server as we previously did manually using `CMD`\n",
    "\n",
    "**Exercise**\n",
    "\n",
    "Edit the file called called `hello_node.docker` in the `dockerfiles` folder to complete all the TODOs."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, build the image in your project using `docker build`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "PROJECT_ID = \"your-gcp-project-here\" # REPLACE WITH YOUR PROJECT NAME\n",
    "os.environ[\"PROJECT_ID\"] = PROJECT_ID"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise**\n",
    "\n",
    "Use `docker build` to build the image from dockerfile you created above. Tag the image as `gcr.io/[project-id]/hello-node:v1`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "TODO: Your code goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It'll take some time to download and extract everything, but you can see the progress bars as the image builds. Once complete, test the image locally by running a Docker container as a daemon on port 8000 from your newly-created container image.\n",
    "\n",
    "**Exercise**\n",
    "\n",
    "Run the container using `docker run` on the port 8000."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "TODO: Your code goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Your output should look something like this:\n",
    "```bash\n",
    "b16e5ccb74dc39b0b43a5b20df1c22ff8b41f64a43aef15e12cc9ac3b3f47cfd\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Right now, since you used the `--d` flag, the container process is running in the background. You can verify it's running using `curl` as before."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!curl http://localhost:8000"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, stop running the container. Get the container id using `docker ps` and then terminate using `docker stop`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!docker ps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# your container id will be different\n",
    "!docker stop b16e5ccb74dc"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that the image is working as intended, push it to the Google Container Registry, a private repository for your Docker images, accessible from your Google Cloud projects. First, configure docker uisng your local config file. The initial push may take a few minutes to complete. You'll see the progress bars as it builds."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!gcloud auth configure-docker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise**\n",
    "\n",
    "Push the `hello-node:v1` image to your Cloud gcr."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "TODO: Your code goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The container image will be listed in your Console. Select `Navigation` > `Container Registry`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create a cluster on GKE\n",
    "\n",
    "Now you're ready to create your Kubernetes Engine cluster. A cluster consists of a Kubernetes master API server hosted by Google and a set of worker nodes. The worker nodes are Compute Engine virtual machines.\n",
    "\n",
    "Create a cluster with two n1-standard-1 nodes (this will take a few minutes to complete). You can safely ignore warnings that come up when the cluster builds.\n",
    "\n",
    "**Note**: You can also create this cluster through the Console by opening the Navigation menu and selecting `Kubernetes Engine` > `Kubernetes clusters` > `Create cluster`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise** \n",
    "\n",
    "Create a GKE cluster in your project using `gcloud container clusters create`. Your cluster should\n",
    " 1. be named `hello-world`\n",
    " 2. have two nodes\n",
    " 3. consist of `n1-standard-1` machines"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "TODO: Your code goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create a pod\n",
    "\n",
    "A Kubernetes pod is a group of containers tied together for administration and networking purposes. It can contain single or multiple containers. Here you'll use one container built with your `Node.js` image stored in your private container registry. It will serve content on port 8000.\n",
    "\n",
    "**Exercise**\n",
    "\n",
    "Create a pod with the `kubectl create` command. You should \n",
    " 1. name your deployment `hello-node`\n",
    " 2. be based on the image you created above called `gcr.io/[project-id]/hello-node:v1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "TODO: Your code goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, you've created a deployment object. Deployments are the recommended way to create and scale pods. Here, a new deployment manages a single pod replica running the `hello-node:v1` image.\n",
    "\n",
    "View the deployment using `kubectl get`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!kubectl get deployments"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly, view the pods created by the deployment by also using `kubectl get`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!kubectl get pods"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here are some other good kubectl commands you should know. They won't change the state of the cluster. Others can be found [here](https://kubernetes.io/docs/reference/kubectl/overview/).\n",
    " * `kubectl cluster-info`\n",
    " * `kubectl config view`\n",
    " * `kubectl get events`\n",
    " * `kubectl logs <pod-name>`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Allow external traffic\n",
    "\n",
    "By default, the pod is only accessible by its internal IP within the cluster. In order to make the hello-node container accessible from outside the Kubernetes virtual network, you have to expose the pod as a Kubernetes service.\n",
    "\n",
    "You can expose the pod to the public internet with the `kubectl expose` command. The `--type=\"LoadBalancer\"` flag is required for the creation of an externally accessible IP. This flag specifies that are using the load-balancer provided by the underlying infrastructure (in this case the Compute Engine load balancer). Note that you expose the deployment, and not the pod, directly. This will cause the resulting service to load balance traffic across all pods managed by the deployment (in this case only 1 pod, but you will add more replicas later).\n",
    "\n",
    "**Exercise**\n",
    "\n",
    "Expose the pod using `kubectl expose` and using the default load-balancer. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "TODO: Your code goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Kubernetes master creates the load balancer and related Compute Engine forwarding rules, target pools, and firewall rules to make the service fully accessible from outside of Google Cloud.\n",
    "\n",
    "To find the publicly-accessible IP address of the service, request kubectl to list all the cluster services."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!kubectl get services"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are 2 IP addresses listed for your `hello-node` service, both serving port 8000. The `CLUSTER-IP` is the internal IP that is only visible inside your cloud virtual network; the `EXTERNAL-IP` is the external load-balanced IP."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should now be able to reach the service by calling `curl http://<EXTERNAL_IP>:8000`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!curl http://34.122.72.240:8000"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point you've gained several features from moving to containers and Kubernetes - you do not need to specify on which host to run your workload and you also benefit from service monitoring and restart. Now see what else can be gained from your new Kubernetes infrastructure."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Scale up your service\n",
    "One of the powerful features offered by Kubernetes is how easy it is to scale your application. Suppose you suddenly need more capacity. You can tell the replication controller to manage a new number of replicas for your pod: \n",
    "```bash\n",
    "kubectl scale deployment hello-node --replicas=4\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Scale your `hello-node` application to have 6 replicas. Then use `kubectl get` to request a description of the updated deployment and list all the pods:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise**\n",
    "\n",
    "Use the `kubectl scale deployment` command to scale up the `hello-node` service to six machines."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "TODO: Your code goes here"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!kubectl get deployment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!kubectl get pods"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A declarative approach is being used here. Rather than starting or stopping new instances, you declare how many instances should be running at all times. Kubernetes reconciliation loops makes sure that reality matches what you requested and takes action if needed.\n",
    "\n",
    "Here's a diagram summarizing the state of your Kubernetes cluster:\n",
    "\n",
    "<img src='../assets/k8s_cluster.png' width=\"500\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Roll out an upgrade to your service\n",
    "At some point the application that you've deployed to production will require bug fixes or additional features. Kubernetes helps you deploy a new version to production without impacting your users.\n",
    "\n",
    "First, modify the application by opening `server.js` so that the response is \n",
    "```bash\n",
    "response.end(\"Hello Kubernetes World!\");\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now you can build and publish a new container image to the registry with an incremented tag (`v2` in this case).\n",
    "\n",
    "**Note**: Building and pushing this updated image should be quicker since caching is being taken advantage of."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise**\n",
    "\n",
    "Build and push the updated image using the `hello_node.docker` file. Tag the updated image as `gcr.io/[project-id]/hello-node:v2`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "TODO: Your code goes here\n",
    "TODO: Your code goes here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Kubernetes will smoothly update your replication controller to the new version of the application. In order to change the image label for your running container, you will edit the existing `hello-node` deployment and change the image from `gcr.io/PROJECT_ID/hello-node:v1` to `gcr.io/PROJECT_ID/hello-node:v2`.\n",
    "\n",
    "To do this, use the `kubectl edit` command. It opens a text editor displaying the full deployment yaml configuration. It isn't necessary to understand the full yaml config right now, just understand that by updating the `spec.template.spec.containers.image` field in the config you are telling the deployment to update the pods with the new image."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Open a terminal and run the following command:\n",
    "\n",
    "```bash \n",
    "kubectl edit deployment hello-node\n",
    "```\n",
    "\n",
    "Look for `Spec` > `containers` > `image` and change the version number to `v2`. This is the output you should see:\n",
    "\n",
    "```bash\n",
    "deployment.extensions/hello-node edited\n",
    "```\n",
    "\n",
    "New pods will be created with the new image and the old pods will be deleted. Run `kubectl get deployments` to confirm. \n",
    "\n",
    "**Note**: You may need to rerun the above command as it provisions machines."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!kubectl get deployments"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "While this is happening, the users of your services shouldn't see any interruption. After a little while they'll start accessing the new version of your application. You can find more details on rolling updates in this documentation.\n",
    "\n",
    "Hopefully with these deployment, scaling, and updated features, once you've set up your Kubernetes Engine cluster, you'll agree that Kubernetes will help you focus on the application rather than the infrastructure."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cleanup\n",
    "\n",
    "Delete the cluster using `gcloud` to free up those resources. Use the `--quiet` flag if you are executing this in a notebook. Deleting the cluster can take a few minutes. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!gcloud container clusters --quiet delete hello-world"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright 2020 Google LLC Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License."
   ]
  }
 ],
 "metadata": {
  "environment": {
   "name": "tf2-gpu.2-1.m59",
   "type": "gcloud",
   "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-1:m59"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
